/**
  ETFAna project, Anyang Normal University && IMP-CAS
  \class ETFPXIUnpacker
  \brief unpacker for raw data collected by PXI daq
  \author SUN Yazhou, asia.rabbit@163.com
  \since 2021-09-27
  \date 2021-10-01 last modified
  \attention
  changelog
  <table>
  <tr>  <th>Date        <th>Author      <th>Description                    </tr>
  <tr>  <td>2021-09-27   <td>Asia Sun    <td>file created                  </tr>
  <tr>  <td>2021-10-01   <td>Asia Sun    <td>completing                    </tr>
  </table>

  \copyright Copyright (c) 2021-2024 Anyang Normal U. && IMP-CAS with LGPLv3 LICENSE
*/

#include <iostream>
#include <climits>
#include <TClonesArray.h>
#include <TTree.h>
#include "ETFRun.h"
#include "ETFPXIUnpacker.h"
#include "ETFPXISection.h"
#include "ETFPXIFragment.h"
#include "ETFHPTDCCh.h"
#include "ETFReadShm.h"
#include "ETFMsg.h"
#include "ETFMath.h"
#include "ETFParaManager.h"
#include "ETFYaml.h"

using std::cout;
using std::endl;

// padding for sim-daq for error events
const section_head ETFPXIUnpacker::kEmptySectionHead;
const fragment_head ETFPXIUnpacker::kEmptyFragmentHead;

ETFPXIUnpacker::ETFPXIUnpacker(int id) : ETFUnpacker(kPXI, id),
fPXISection(new ETFPXISection(id)), fBunchIDMisAlignCnt(0), fIsCheckBunchID(true){
  const auto &cc = (*fpm->GetMisPara())["checkBunchId"];
  if(cc) fIsCheckBunchID = cc.as<bool>();
} // end ctor

ETFPXIUnpacker::~ETFPXIUnpacker(){
  if(fPXISection) delete fPXISection;
} // end dtor

void ETFPXIUnpacker::Init(){
  Register();
} // end member function Init

void ETFPXIUnpacker::Register(){
  TTree *tree = ETFRun::Instance()->GetTree();
  tree->Branch(Form("pxi%d", fId), fPXISection);
  tree->Branch(Form("tchpxi%d", fId), &fPXISection->fHPTDCChannels);

  // sprintf(name, "frag%d", fId);
  // fTree->Register(name, fPXISection->fFragments);
  // sprintf(name, "achpxi%d", fId);
  // fTree->Register(name, fPXISection->fAmplitudeChannels);
} // end member function Register

void ETFPXIUnpacker::Reset(){
  memset(fData, 0, sizeof(fData));
  for(int i = fPXISection->sec_h().fragment_num; i--;) fFrag_h[i].initialize();
  fPXISection->Reset(); // clear the fragments
} // end member function Reset

//----------------------------PXI---------------------------------------------//
//---------------------------UNPACK-------------------------------------------//
//----------------------------CODE--------------------------------------------//

// get one event from offline data file to memory (fData)
// \retval: return index of the event being read
int ETFPXIUnpacker::GetOneEvent(FILE *fp){
  section_head &sec_h = fPXISection->fSectionHead;
  // read the section header, retrieve the number of valid fragments
  if(fread(&sec_h, sizeof(section_head), 1, fp) <= 0){ feof = true; return UINT_MAX; }
  fCurrentEventIndex = sec_h.index;
  fPXISection->fSection_len += sizeof(section_head);
  int frag_nu = sec_h.fragment_num;
  if(frag_nu <= 0){
    ETFMsg::Info("ETFPXIUnpacker", "GetOneEvent: This event has no data, "
      "event index: %d, fragment_num: %d\r", sec_h.index, frag_nu);
  } // end if
  else{ // read all the fragment head
    if(frag_nu >= FRAG_NUM_LIMIT_PER_EVENT){
      ETFMsg::Info("ETFPXIUnpacker", "GetOneEvent: fragment number out of limit");
      sec_h.fragment_num = frag_nu = 0;
      fError = true;
    } // end if
    if(fread(fFrag_h, sizeof(fragment_head), frag_nu, fp) <= 0) return -1;
    fPXISection->fSection_len += frag_nu * sizeof(fragment_head);
  } // end else
  //----------------- read the data zone of the fragments -----------------//
  if(frag_nu){
    int total_data_len = 0; // length of the data zone of the event
    for(int i = 0; i < frag_nu; i++){
      if(fFrag_h[i].data_len > 0) total_data_len += fFrag_h[i].data_len;
      else if(fFrag_h[i].data_len < 0){
        // ETFMsg::Info("ETFPXIUnpacker", "GetOneEvent: minus data_len in frag [%d]: %d",
        //   i, fFrag_h[i].data_len);
        fFrag_h[i].data_len = 0;
        // fFrag_h[i].print();
        fError = true;
      } // end else
    } // end for over fragment heads
    if(total_data_len + fPXISection->fSection_len > kMAX_SIZE){
      ETFMsg::Info("ETFPXIUnpacker", "GetOneEvent: data_len too large");
      fError = true;
    } // end if
    else if(total_data_len){
      if(fread(fData, sizeof(int), total_data_len/sizeof(int), fp) <= 0) return -1;
      fPXISection->fSection_len += total_data_len;
    } // end if
  } // end if
  fSizeCnt += fPXISection->fSection_len;
  fPXISection->fSection_len /= sizeof(int); // byte => word
  if(fReadShm) WriteOneEvent();

  return sec_h.index; // reading of one event is now complete
} // end member function GetOneEvent(FILE *fp)

void ETFPXIUnpacker::WriteOneEvent(){
  if(fPXISection->fSection_len + 1 > int(sizeof(fOnline)/sizeof(int))) return;

  memset(fOnline, 0, sizeof(fOnline));
  int pos = 0, size = 0;
  fOnline[0] = fPXISection->fSection_len;
  char *p = reinterpret_cast<char *>(&fOnline[1]); // to facilitate memcpy
  // copy section_head and fragment_heads
  section_head &sec_h = fPXISection->fSectionHead;
  if(fError) sec_h.fragment_num = 0; // invalidate this event
  size = sizeof(section_head);
  memcpy(p, &sec_h, size); pos += size;
  const int frag_nu = sec_h.fragment_num;
  if(frag_nu){
    size = sizeof(fragment_head) * frag_nu;
    memcpy(p + pos, fFrag_h, size); pos += size;
    // copy the data zone
    size = fPXISection->fSection_len * sizeof(int) - pos;
    if(size > 0) memcpy(p + pos, fData, size);
    // invalidate it again
    else if(size < 0) reinterpret_cast<section_head *>(p)->fragment_num = 0;
  } // end if

  // if(!fReadShm) ETFMsg::Error("ETFPXIUnpacker", "WriteOneEvent: fReadShm not assigned.");
  fReadShm->write(fOnline);
} // end member function WriteOneEvent

// get one event from online data stream to memory
int ETFPXIUnpacker::GetOneEvent(int *data){
  int &pos = fPXISection->fSection_len;
  pos = 0;
  char *p = reinterpret_cast<char *>(&data[1]); // data[0] is ev_len in words
  section_head &sec_h = fPXISection->fSectionHead;
  // read the section header, retrieve the number of valid fragments
  int size = sizeof(section_head);
  memcpy(&sec_h, p + pos, size); pos += size;
  fCurrentEventIndex = sec_h.index;
  int frag_nu = sec_h.fragment_num;
  if(frag_nu >= FRAG_NUM_LIMIT_PER_EVENT){
    ETFMsg::Warn("ETFPXIUnpacker", "GetOneEvent(int *): frag_nu out of limit");
    sec_h.fragment_num = frag_nu = 0;
    fError = true;
  } // end if
  if(frag_nu > 0){
    size = sizeof(fragment_head)*frag_nu;
    memcpy(fFrag_h, p + pos, size); pos += size;  // read all the fragment heads
  } // end if
  //----------------- read the data zone of the fragments -----------------//
  int total_data_len = 0; // length of the data zone of the event
  const bool empty = 6 == data[0]; // empty: 1 sec_head+1 fra_head = 6 words
  if(!empty && frag_nu){
    for(int i = 0; i < frag_nu; i++){
      if(fFrag_h[i].data_len > 0) total_data_len += fFrag_h[i].data_len;
      else{
        fFrag_h[i].data_len = 0;
        fError = true;
      } // end else
    } // end for over fragment heads
    if(total_data_len + pos >= kMAX_SIZE){ // the 1st int stores the ev_length
      ETFMsg::Info("ETFPXIUnpacker", "GetOneEvent(int *): data_len too large in "
        "event %d, data[0]: %d", sec_h.index, data[0]);
      fError = true;
    } // end if
    else if(total_data_len){
      memcpy(fData, p + pos, total_data_len);
      pos += total_data_len;
    } // end if
    else fError = true;
  } // end if

  pos /= sizeof(int); // byte => word
  if(pos != data[0] && fError){
    ETFMsg::Info("ETFPXIUnpacker", "GetOneEvent(int *): Some error(s) occurred, pos: "
      "%d, data[0]: %d, frag_nu: %d, tot_len: %d", pos, data[0], frag_nu, total_data_len);
  } // end if

  // cout << "-------: PXI index: " << fCurrentEventIndex << endl; // DEBUG
  return fCurrentEventIndex;
} // end member function GetOneEvent(int *data)

// do the rest of the unpacking after GetOneEvent and store the results in
// member variable fPXISection
// NOTE that the method is called by ETFOfflineSource::ReadEvent, where Unpack()
// would be bypassed for failure of GetOneEvent()
bool ETFPXIUnpacker::Unpack(){
  if(fError){ // abnormal event
    fEventCnt++;
    fError = false;
    return fPXISection->fUnpackGood = false;
  } // end if

  int pos = 0; // pointer offset to the current position in the stream
  int pos0 = 0; // to record pos at each start of a fragment - avert data_
  int va_ch_nu = 0; // valid channel number (e.g.: 16, 128)
  const int frag_nu = fPXISection->fSectionHead.fragment_num;
  TClonesArray *frags = fPXISection->GetFragments();
  TClonesArray *tchs = fPXISection->GetHPTDCChannels();

  // here we start from the data zone part, i.e. the two tables and the channel data
  for(int i = 0; i < frag_nu; i++){ // loop over fragments
    ETFPXIFragment *frag = dynamic_cast<ETFPXIFragment *>(frags->ConstructedAt(i));
    frag->Reset();
    bool &bad_frag = frag->fIsBad; // whether the fragment is bad
    frag->fFragmentHead = fFrag_h[i];
    const int ch_nu = fFrag_h[i].channel_num;
    //----------------------- READ DATA_VALIDITY_TABLE -------------------------//
    int *ch_id = nullptr; // channel id array
    int data_len = fFrag_h[i].data_len; // data length (including possible tables)
    if(0 == data_len){ bad_frag = true; continue; }
    pos = pos0; pos0 += data_len / sizeof(int); // points to the current frag
    if(fFrag_h[i].channel_full){ // all the channels in the fragment are fired
      va_ch_nu = ch_nu;
      ch_id = fChId;
      for(int j = 0; j < va_ch_nu; j++) ch_id[j] = fFrag_h[i].start_channel + j;
    } // end if(channel_full)
    else{ // not all the channels in the fragment are fired, so there is a data_validity_table
      va_ch_nu = *(fData + pos);
      if(pos >= kMAX_LEN){
        ETFMsg::Warn("ETFPXIUnpacker", "Unpack: illegal pos: %d", pos);
        bad_frag = true; continue; // avert illegal memory access
      } // end if
      pos++;
      if(va_ch_nu <= 0 || va_ch_nu > ch_nu){ // abnormal va_ch_nu
        ETFMsg::Warn("ETFPXIUnpacker", "Unpack: abnormal va_ch_nu: %d", va_ch_nu);
        bad_frag = true; continue;
      } // end if
      // read channel id of every fired channels
      ch_id = fData + pos;
      if(pos >= kMAX_LEN){
        ETFMsg::Warn("ETFPXIUnpacker", "Unpack: illegal pos: %d", pos);
        bad_frag = true; continue; // avert illegal memory access
      } // end if
      pos += va_ch_nu;
      data_len -= (va_ch_nu + 1) * sizeof(int); // subtract the length of the data_validity_table
    } // end else
    //----------------------- READ CHANNEL_OFFSET_TABLE ----------------------//
    int *edge_num = fEdgeNum; // number of edges for each channel
    int length_last_ch = -1; // length of the last channel of a fragment, see below
    if(fFrag_h[i].length_change){ // a channel_offset_table exists
      int *offset = fData + pos;
      if(pos >= kMAX_LEN){
        ETFMsg::Warn("ETFPXIUnpacker", "Unpack: (ch_ofst_table) illegal pos: %d", pos);
        bad_frag = true; continue; // avert illegal memory access
      } // end if
      pos += va_ch_nu;
      // obtain edge number of each fired channel
      for(int j = 0; j < va_ch_nu - 1; j++)
        edge_num[j] = (offset[j + 1] - offset[j]) / sizeof(int);
      data_len -= va_ch_nu * sizeof(int); // subtract the length of the channel_offset_table
      length_last_ch = (data_len - offset[va_ch_nu - 1]) / sizeof(int); // length in words
    } // end if(length_change)
    else{ // the edge numbers of each channel are the same
      length_last_ch = data_len / (va_ch_nu * sizeof(int));
      for(int j = 0; j < va_ch_nu - 1; j++) edge_num[j] = length_last_ch;
    } // end else
    // ----------------------- PROCESS EACH CHANNEL ------------------------- //
    // loop over fired channels except the last one, which is explained later
    for(int j = 0; j < va_ch_nu - 1; j++) if(edge_num[j] > 0){
      ETFHPTDCCh *ch =
        dynamic_cast<ETFHPTDCCh *>(tchs->ConstructedAt(tchs->GetEntriesFast()));
      ch->Reset();
      ch->SetChannelId(ch_id[j]);
      int *data_ch = fData + pos;
      if(pos >= kMAX_LEN){
        ETFMsg::Warn("ETFPXIUnpacker", "Unpack: (edge_num) illegal pos: %d", pos);
        bad_frag = true;
        continue; // avert illegal memory access
      } // end if
      pos += edge_num[j];
      // reading the edges for each channel //
      for(int k = 0; k < edge_num[j]; k++) if(k < EDGE_NUM_LIMIT){
        // the 31st bit of edge[j] indicates if it's a HIGH or VERY HIGH channel
        if(data_ch[k]>>31 & 0x1){ // VERY HIGH resolution mode
          if(33 != ch_nu){
            ETFMsg::Warn("ETFPXIUnpacker", "Unpack: VERY HIGH, but ch_nu is %d", ch_nu);
            bad_frag = true;
            continue; // avert illegal memory access
          } // end if
          if(data_ch[k]>>30 & 0x1) ch->AddTrailingEdgeV(data_ch[k] & 0x1FFFFF);
          else ch->AddLeadingEdgeV(data_ch[k] & 0x1FFFFF);
        } // end if high
        else{ // HIGH resolution mode
          if(data_ch[k]>>30 & 0x1){
            if(33 == ch_nu) ch->AddTrailingEdgeH(data_ch[k] & 0x7FFFF); // TOF card
            else ch->AddLeadingEdgeH(data_ch[k] & 0x7FFFF); // MWDC card
          } // end if 30th bit
          else{
            if(33 == ch_nu) ch->AddLeadingEdgeH(data_ch[k] & 0x7FFFF); // TOF card
            else ch->AddTrailingEdgeH(data_ch[k] & 0x7FFFF); // MWDC card
          } // end else
        } // end else HIGH
      } // end for and if over edges
    } // end loop over valid channels except the last one which is explained below
    // --------------- process the last channel ---------------------- //
    // The last channel of every fired fragment only stores a group header defined
    // by HPTDC@CERN, containing trigger (bunchID) and event ID for the fragment
    if(length_last_ch > 0){
      int *data_ch_last = fData + pos;
      if(pos >= kMAX_LEN){
        ETFMsg::Warn("ETFPXIUnpacker", "Unpack: (last_ch) illegal pos: %d", pos);
        bad_frag = true; continue; // avert illegal memory access
      } // end if
      frag->fBunchID = data_ch_last[0] & 0xFFF;
      if(0 == i) fPXISection->fBunchID = frag->fBunchID;
      frag->fEventID = (data_ch_last[0]>>12) & 0xFFF;
      // set fragment-specific bunch id //
      int nch = tchs->GetEntriesFast();
      for(int j = 0; j < va_ch_nu - 1; j++) if(edge_num[j] > 0){
        auto *ch = dynamic_cast<ETFHPTDCCh *>(tchs->At(--nch));
        // ch->SetBunchID();
        ch->SubtractTrigger(frag->fBunchID);
      } // end for over j
    } // end if
    else
      ETFMsg::Warn("ETFPXIUnpacker", "Unpack: length of last channel <= 0: %d", length_last_ch);
    if(1 != length_last_ch){
      // ETFMsg::Info("ETFPXIUnpacker", "Unpack: length of last channel is not 1 word");
      ETFMsg::Info("ETFPXIUnpacker", "Unpack: index %d, frag_num %d, length_last_ch is %d words",
        fCurrentEventIndex, i, length_last_ch);
    } // end if
  } // end loop over fragments

  //--------- data integrity check to conclude the section ------------//
  if(frags->GetEntriesFast() > 1){ // at least 2 frags are needed for the checking
    TIter n(frags);
    const int bunchid0 = dynamic_cast<ETFPXIFragment *>(*n.Begin())->GetBunchID();
    const int eventid0 = dynamic_cast<ETFPXIFragment *>(*n.Begin())->GetEventID();
    /// bunchID of the 1st fragment
    ETFPXIFragment *frag;
    while((frag = dynamic_cast<ETFPXIFragment *>(n()))){
      if(frag->IsBad()){
        fPXISection->fUnpackGood = false;
        continue;
      } // end if
      // currently there're only TOF (33) and MWDC (129) cards //
      const int ch_nu = frag->fFragmentHead.channel_num;
      if(33 != ch_nu && 129 != ch_nu){
        ETFMsg::Warn("ETFPXIUnpacker",
          "Unpack: Odd... Channel number of one fragment doesn't equal 33 or 129");
        frag->Print();
      } // end if
      // all fragments should have the same bunchId and eventID //
      if(bunchid0 != frag->GetBunchID() || eventid0 != frag->GetEventID()){
        fBunchIDMisAlignCnt++;
        if(fIsCheckBunchID){
          ETFMsg::Info("ETFPXIUnpacker", "Unpack: BunchID or EventID mismatch!");
          fPXISection->PrintBunchID();
          fPXISection->PrintEventID();
        } // end if
        break;
      } // end if
    } // end while
  } // end if(nof frags > 1)

  fEventCnt++;
  return fPXISection->fUnpackGood;
} // end member function Unpack

void ETFPXIUnpacker::PrintSummary(){
  cout << "  PXI unpacker " << fId << ": ";
  cout << "\033[32;1m" << filename() << "\033[0m unpacked, ";
  cout << "\033[1m" << fEventCnt << "\033[0m events analysed" << endl;

  cout << "\033[32;1mCurrent Event Index: " << fCurrentEventIndex << "\033[0m" << endl;

  if(fIsCheckBunchID)
    cout << "BunchID misalignment count happened \033[36;1m"
         << fBunchIDMisAlignCnt << " times\033[0m\n";
} // end member function PrintSummary

bool ETFPXIUnpacker::UnpackGood() const{
  return fPXISection->UnpackGood();
} // end member function UnpackStatus

// Print the unpacked data of the current event
void ETFPXIUnpacker::Print(){
  ETFUnpacker::Print();
  fPXISection->Print();
} // end member function Print
